/*
 * Copyright (C) 2009-2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

#include <stdio.h>

#define dputc(ch)		eputc(std_output, ch)
#define dputs(object, length)	ewrite(std_output, object, length)

#define print_ast(value)	write_ast(value, indent, format)
#define print_obj(value)	write_object(value, format)
#define print_int(value)	eprint_wrd(std_output, format, value)
#define print_flt(value)	eprint_flt(std_output, format, value)
#define print_str(value)	eprint_str(std_output, format, value)
#define print_rat(value)	eprint_rat(std_output, format, value)
#define print_mpz(value)	eprint_mpz(std_output, format, value)
#define print_mpq(value)	eprint_mpq(std_output, format, value)
#define print_mpr(value)	eprint_mpr(std_output, format, value)
#define print_cdd(value)	eprint_cdd(std_output, format, value)
#define print_cqq(value)	eprint_cqq(std_output, format, value)
#define print_mpc(value)	eprint_mpc(std_output, format, value)
#define print_ptr(value)	eprint_ptr(std_output, format, value)
#define print_sym(value)	dputs(value->name->v.obj, value->name->length)
#define print_vi8(vec)		write_i8_vector(vec->v.obj, vec->length, format)
#define print_vi16(vec)		write_i16_vector(vec->v.obj, vec->length, format)
#define print_vu16(vec)		write_u16_vector(vec->v.obj, vec->length, format)
#define print_vi32(vec)		write_i32_vector(vec->v.obj, vec->length, format)
#define print_vu32(vec)		write_u32_vector(vec->v.obj, vec->length, format)
#define print_vi64(vec)		write_i64_vector(vec->v.obj, vec->length, format)
#define print_vu64(vec)		write_u64_vector(vec->v.obj, vec->length, format)
#define print_vf32(vec)		write_f32_vector(vec->v.obj, vec->length, format)
#define print_vf64(vec)		write_f64_vector(vec->v.obj, vec->length, format)
#define print_vobj(vec)		write_obj_vector(vec->v.obj, vec->length, format)
#define print_tag(tag, name, fields)					\
    write_tag(tag, name, fields, indent, format)
#define print_fields(value)	write_fields(value, indent, format)
#define print_ast_list(value)	write_ast_list(value, indent, format)
#define print_ast_call(value)	write_ast_call(value, indent, format)
#define print_ast_unary(string, length, value)				\
    write_ast_unary(string, length, value, indent, format)
#define print_ast_binary(string, length, value)				\
    write_ast_binary(string, length, value, indent, format)

#define print_ast_binary_function(string, length, value)		\
    write_ast_binary_function(string, length, value, indent, format)

#define print_ast_comma_list(value)					\
    write_ast_comma_list(value, indent, format)
#define print_ast_paren_comma_list(value)				\
    write_ast_paren_comma_list(value, indent, format)
#define print_ast_newline()	write_ast_newline(indent)
#define print_ast_type(ast, left)					\
    do {								\
	if (cfg_verbose > 1)						\
	    bytes += write_ast_type(ast->type, left);			\
    } while (0)
#define print_ast_type_left(ast)	print_ast_type(ast, true)
#define print_ast_type_right(ast)	print_ast_type(ast, false)

/*
 * Prototypes
 */
static eword_t
write_object(eobject_t object, eformat_t *format);

#define write_vector(id, type)						\
static eword_t								\
write_##id##_vector(type *pointer, eword_t length, eformat_t *format)
write_vector(i8, eint8_t);
write_vector(i16, eint16_t);
write_vector(u16, euint16_t);
write_vector(i32, eint32_t);
write_vector(u32, euint32_t);
write_vector(i64, eint64_t);
write_vector(u64, euint64_t);
write_vector(f32, efloat32_t);
write_vector(f64, efloat64_t);
write_vector(obj, eobject_t);
#undef write_vector

static eword_t
write_ast(east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_ast_list(east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_ast_call(east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_ast_unary(char *string, eword_t length,
		 east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_ast_binary(char *string, eword_t length,
		 east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_ast_binary_function(char *string, eword_t length,
			  east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_ast_comma_list(east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_ast_paren_comma_list(east_t *ast, eint32_t indent, eformat_t *format);

static eword_t
write_fields(erecord_t *record, eint32_t indent, eformat_t *format);

static eword_t
write_tag(etag_t *tag, esymbol_t *name, ebool_t fields,
	  eint32_t indent, eformat_t *format);

static eword_t
write_ast_newline(eint32_t indent);

static eword_t
write_ast_type(etype_t type, ebool_t left);

/*
 * Implementation
 */
void
init_debug(void)
{
}

void
finish_debug(void)
{
}

void
ewrite_object(eobject_t object)
{
    eformat_t	 local;
    eformat_t	*format;

    format = &local;
    memset(format, 0, sizeof(eformat_t));
    local.read = 1;
    local.radix = 10;
    print_obj(object);
    dputc('\n');
    eflush(std_output);
}

static eword_t
write_object(eobject_t object, eformat_t *format)
{
    etype_t		 type;
    eword_t		 bytes;
    eentry_t		*entry;
    ebool_t		 first;
    eint32_t		 indent;
    eword_t		 offset;
    union {
	east_t		*ast;
	ecdd_t		*cdd;
	ecqq_t		 cqq;
	eentry_t	*entry;
	ehash_t		*hash;
	eword_t		*wptr;
	efloat_t	*fptr;
	erat_t		 rat;
	empz_t		 mpz;
	empq_t		 mpq;
	empr_t		 mpr;
	empc_t		 mpc;
	eobject_t	 object;
	erecord_t	*record;
	esymbol_t	*symbol;
	etag_t		*tag;
	evalue_t	*value;
	evector_t	*vector;
    } data;

    if ((data.object = object) == null)
	bytes = dputs("null", 4);
    else {
	switch (type = etype(object)) {
	    case t_ast:
		indent = 0;
		bytes = print_ast_list(data.ast);
		break;
	    case t_basic:	case t_class:
		print_sym(data.record->name);
		break;
	    case t_word:
		bytes = print_int(*data.wptr);
		break;
	    case t_float:
		bytes = print_flt(*data.fptr);
		break;
	    case t_rat:
		bytes = print_rat(data.rat);
		break;
	    case t_mpz:
		bytes = print_mpz(data.mpz);
		break;
	    case t_mpq:
		bytes = print_mpq(data.mpq);
		break;
	    case t_mpr:
		bytes = print_mpr(data.mpr);
		break;
	    case t_cdd:
		bytes = print_cdd(data.cdd);
		break;
	    case t_cqq:
		bytes = print_cqq(data.cqq);
		break;
	    case t_mpc:
		bytes = print_mpc(data.mpc);
		break;
	    case t_string:
		bytes = print_str(data.vector);
		break;
	    case t_hash:
		first = true;
		bytes = dputs("hash_t {", 8);
		for (offset = 0; offset < data.hash->size; offset++) {
		    entry = data.hash->entries[offset];
		    for (; entry; entry = entry->next) {
			if (first) {
			    first = false;
			    dputc(' ');	++bytes;
			}
			else
			    bytes += dputs(", ", 2);
			bytes += print_obj(entry);
		    }
		}
		bytes += dputs(" }", 2);
		break;
	    case t_entry:
		bytes = dputs("entry_t { name: ", 16);
		bytes += print_obj(data.entry->name);
		bytes += dputs(", value: ", 9);
		bytes += print_obj(data.entry->value);
		dputc('}');		++bytes;
		break;
	    case t_symbol:
		bytes = print_sym(data.symbol);
		break;
	    case t_value:
		data.symbol = data.value->name;
		bytes = print_sym(data.symbol);
		break;
	    case t_tag:
		indent = 0;
		bytes = print_tag(data.tag, null, false);
		break;
	    default:
		if (type & t_vector) {
		    if (data.vector->length) {
			type &= ~t_vector;
			bytes = dputs("{ ", 2);
			switch (type) {
			    case t_int8:
				bytes += print_vi8(data.vector);
				break;
			    case t_int16:
				bytes += print_vi16(data.vector);
				break;
			    case t_uint16:
				bytes += print_vu16(data.vector);
				break;
			    case t_int32:
				bytes += print_vi32(data.vector);
				break;
			    case t_uint32:
				bytes += print_vu32(data.vector);
				break;
			    case t_int64:
				bytes += print_vi64(data.vector);
				break;
			    case t_uint64:
				bytes += print_vu64(data.vector);
				break;
			    case t_float32:
				bytes += print_vf32(data.vector);
				break;
			    case t_float64:
				bytes += print_vf64(data.vector);
				break;
			    default:
				bytes += print_vobj(data.vector);
				break;
			}
			bytes += dputs(" }", 2);
		    }
		    else
			bytes = dputs("{ }", 3);
		}
		else
		    bytes = 0;
		break;
	}
    }

    return (bytes);
}

#define write_int_vector(id, type)					\
static eword_t								\
write_##id##_vector(type *pointer, eword_t length, eformat_t *format)	\
{									\
    eword_t		bytes;						\
									\
    for (bytes = 0; length > 1; length--, pointer++, bytes++) {		\
	bytes += print_int(*pointer);					\
	bytes += dputs(", ", 2);					\
    }									\
    bytes += print_int(*pointer);					\
									\
    return (bytes);							\
}
write_int_vector(i8, eint8_t)
write_int_vector(i16, eint16_t)
write_int_vector(u16, euint16_t)
write_int_vector(i32, eint32_t)
#  if __WORDSIZE == 64
write_int_vector(u32, euint32_t)
write_int_vector(i64, eint64_t)
#  endif
#undef write_int_vector

#if __WORDSIZE == 32
static eword_t
write_u32_vector(euint32_t *pointer, eword_t length, eformat_t *format)
{
    eword_t	bytes;
    eword_t	word;

    for (bytes = 0; length > 1; length--, pointer++, bytes++) {
	word = *pointer;
	if (likely(word >= 0))
	    bytes += print_int(word);
	else {
	    mpz_set_ui(thr_zr, word);
	    bytes += print_mpz(thr_zr);
	}
	bytes += dputs(", ", 2);
    }
    word = *pointer;
    if (likely(word >= 0))
	bytes += print_int(word);
    else {
	mpz_set_ui(thr_zr, word);
	bytes += print_mpz(thr_zr);
    }

    return (bytes);
}

static eword_t
write_i64_vector(eint64_t *pointer, eword_t length, eformat_t *format)
{
    eword_t	bytes;
    eint64_t	integer;

    for (bytes = 0; length > 1; length--, pointer++, bytes++) {
	integer = *pointer;
	if ((eword_t)integer == integer)
	    bytes += print_int(integer);
	else {
	    empz_set_ll(thr_zr, integer);
	    bytes += print_mpz(thr_zr);
	}
	bytes += dputs(", ", 2);
    }
    integer = *pointer;
    if ((eword_t)integer == integer)
	bytes += print_int(integer);
    else {
	empz_set_ll(thr_zr, integer);
	bytes += print_mpz(thr_zr);
    }

    return (bytes);
}
#endif

static eword_t
write_u64_vector(euint64_t *pointer, eword_t length, eformat_t *format)
{
    eword_t	bytes;
    eint64_t	integer;

    for (bytes = 0; length > 1; length--, pointer++, bytes++) {
	integer = *pointer;
	if (
#if __WORDSIZE == 32
	    (eword_t)integer == integer &&
#endif
	    integer > 0
	    )
	    bytes += print_int(integer);
	else {
	    empz_set_ull(thr_zr, integer);
	    bytes += print_mpz(thr_zr);
	}
	bytes += dputs(", ", 2);
    }
    integer = *pointer;
    if (likely(integer >= 0))
	bytes += print_int(integer);
    else {
	empz_set_ull(thr_zr, integer);
	bytes += print_mpz(thr_zr);
    }

    return (bytes);
}

#define write_flt_vector(id, type)					\
static eword_t								\
write_##id##_vector(type *pointer, eword_t length, eformat_t *format)	\
{									\
    eword_t		bytes;						\
									\
    for (bytes = 0; length > 1; length--, pointer++, bytes++) {		\
	bytes += print_flt(*pointer);					\
	bytes += dputs(", ", 2);					\
    }									\
    bytes += print_flt(*pointer);					\
									\
    return (bytes);							\
}
write_flt_vector(f32, efloat32_t)
write_flt_vector(f64, efloat64_t)
#undef write_flt_vector

static eword_t
write_obj_vector(eobject_t *pointer, eword_t length, eformat_t *format)
{
    eword_t		bytes;

    for (bytes = 0; length > 1; length--, pointer++, bytes++) {
	bytes += print_obj(*pointer);
	bytes += dputs(", ", 2);
    }
    bytes += print_obj(*pointer);

    return (bytes);
}

static eword_t
write_ast(east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		 bytes;
    esymbol_t		*symbol;
    evector_t		*vector;

    bytes = 0;
    switch (ast->token) {
	case tok_number:
	    bytes += print_obj(ast->l.value);
	    break;
	case tok_symbol:
	    symbol = ast->l.value;
	    bytes += print_sym(symbol);
	    if (cfg_verbose > 0) {
		dputc('#');	++bytes;
		bytes += print_int(ast->code);
	    }
	    break;
	case tok_string:
	    vector = ast->l.value;
	    bytes += print_str(vector);
	    break;
	case tok_class:
	    if (ast->l.ast->token == tok_type)
		bytes += print_ast(ast->l.ast);
	    else {
		bytes += dputs("class ", 6);
		bytes += print_ast_comma_list(ast->l.ast);
	    }
	    dputc(';');		++bytes;
	    break;
	case tok_type:
	    bytes += print_tag(ast->l.value, null, true);
	    break;
	case tok_defn:
	    bytes += print_tag(ast->l.value, null, false);
	    break;
	case tok_declexpr:
	    assert(ast->l.ast && ast->l.ast->token == tok_type);
	    bytes += print_tag(ast->l.ast->l.value, null, false);
	    dputc(' ');		++bytes;
	    bytes += print_ast(ast->r.ast);
	    break;
	case tok_decl:
	    assert(ast->l.ast && ast->l.ast->token == tok_type);
	    bytes += print_tag(ast->l.ast->l.value, null, false);
	    dputc(' ');		++bytes;
	    bytes += print_ast_comma_list(ast->r.ast);
	    dputc(';');		++bytes;
	    break;
	case tok_oparen:	dputc('(');		++bytes;	break;
	case tok_cparen:	dputc(')');		++bytes;	break;
	case tok_obrack:	dputc('[');		++bytes;	break;
	case tok_cbrack:	dputc(']');		++bytes;	break;
	case tok_obrace:	dputc('{');		++bytes;	break;
	case tok_cbrace:	dputc('}');		++bytes;	break;
	case tok_collon:	dputc(':');		++bytes;	break;
	case tok_semicollon:	dputc(';');		++bytes;	break;
	case tok_comma:		dputc(',');		++bytes;	break;
	case tok_ellipsis:	bytes = dputs("...", 3);		break;
	case tok_dot:
	    bytes = print_ast(ast->l.ast);
	    dputc('.');		++bytes;
	    bytes += print_ast(ast->r.ast);
	    break;
	case tok_set:
	    bytes += print_ast_binary("=", 1, ast);
	    break;
	case tok_andset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("&=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_orset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("|=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_xorset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("^=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_mul2set:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("<<=", 3, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_div2set:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary(">>=", 3, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_shlset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("<<<=", 4, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_shrset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary(">>>=", 4, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_addset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("+=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_subset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("-=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_mulset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("*=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_divset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("/=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_trunc2set:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("\\=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_remset:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("%=", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_andand:
	    bytes += print_ast_binary("&&", 2, ast);
	    break;
	case tok_oror:
	    bytes += print_ast_binary("||", 2, ast);
	    break;
	case tok_lt:
	    bytes += print_ast_binary("<", 1, ast);
	    break;
	case tok_le:
	    bytes += print_ast_binary("<=", 2, ast);
	    break;
	case tok_eq:
	    bytes += print_ast_binary("==", 2, ast);
	    break;
	case tok_ge:
	    bytes += print_ast_binary(">=", 2, ast);
	    break;
	case tok_gt:
	    bytes += print_ast_binary(">", 1, ast);
	    break;
	case tok_ne:
	    bytes += print_ast_binary("!=", 2, ast);
	    break;
	case tok_and:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("&", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_or:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("|", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_xor:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("^", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_mul2:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("<<", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_div2:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary(">>", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_shl:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("<<<", 3, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_shr:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary(">>>", 3, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_add:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("+", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_sub:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("-", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_mul:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("*", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_div:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("/", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_trunc2:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("\\", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_rem:
	    print_ast_type_left(ast);
	    bytes += print_ast_binary("%", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_inc:
	    print_ast_type_left(ast);
	    bytes += print_ast_unary("++", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_dec:
	    print_ast_type_left(ast);
	    bytes += print_ast_unary("--", 2, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_postinc:
	    print_ast_type_left(ast);
	    bytes += print_ast(ast->l.ast);
	    bytes += dputs("++", 2);
	    print_ast_type_right(ast);
	    break;
	case tok_postdec:
	    print_ast_type_left(ast);
	    bytes += print_ast(ast->l.ast);
	    bytes += dputs("--", 2);
	    print_ast_type_right(ast);
	    break;
	case tok_plus:
	    print_ast_type_left(ast);
	    bytes += print_ast_unary("+", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_neg:
	    print_ast_type_left(ast);
	    bytes += print_ast_unary("-", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_not:
	    bytes += print_ast_unary("!", 1, ast);
	    break;
	case tok_com:
	    print_ast_type_left(ast);
	    bytes += print_ast_unary("~", 1, ast);
	    print_ast_type_right(ast);
	    break;
	case tok_atan2:
	    bytes += print_ast_binary_function("atan2", 5, ast);
	    break;
	case tok_pow:
	    bytes += print_ast_binary_function("pow", 3, ast);
	    break;
	case tok_hypot:
	    bytes += print_ast_binary_function("hypot", 5, ast);
	    break;
	case tok_complex:
	    bytes += print_ast_binary_function("complex", 7, ast);
	    break;
	case tok_subtypeof:
	    bytes += print_ast_binary_function("subtypeof", 9, ast);
	    break;
	case tok_sizeof:
	    bytes += dputs("sizeof(", 7);
	    bytes += print_ast(ast->l.ast);
	    dputc(')');		++bytes;
	    break;
	case tok_new:
	    bytes += dputs("new(", 4);
	    assert(ast->l.ast->token == tok_type);
	    bytes += print_tag(ast->l.value, null, false);
	    dputc(')');		++bytes;
	    break;
	case tok_goto:
	    bytes += dputs("goto ", 5);
	    bytes += print_obj(ast->l.ast->l.value);
	    dputc(';');		++bytes;
	    break;
	case tok_question:
	    print_ast_type_left(ast);
	    bytes += print_ast(ast->t.ast);
	    bytes += dputs(" ? ", 3);
	    bytes += print_ast(ast->l.ast);
	    bytes += dputs(" : ", 3);
	    bytes += print_ast(ast->r.ast);
	    print_ast_type_right(ast);
	    break;
	case tok_return:
	    if (ast->l.ast) {
		bytes += print_ast_unary("return ", 7, ast);
		dputc(';');	++bytes;
	    }
	    else
		bytes += dputs("return;", 7);
	    break;
	case tok_break:
	    bytes += dputs("break;", 6);
	    break;
	case tok_continue:
	    bytes += dputs("continue;", 9);
	    break;
	case tok_list:
	    print_ast_type_left(ast);
	    bytes += print_ast_paren_comma_list(ast->l.ast);
	    print_ast_type_right(ast);
	    break;
	case tok_vector:
	    bytes += print_ast(ast->l.ast);
	    dputc('[');		++bytes;
	    bytes += print_ast(ast->r.ast);
	    dputc(']');		++bytes;
	    if (cfg_verbose > 0) {
		dputc('#');	++bytes;
		bytes += print_int(ast->code);
	    }
	    break;
	case tok_stat:
	    bytes += print_ast_newline();
	    bytes += print_ast_comma_list(ast->l.ast);
	    dputc(';');		++bytes;
	    break;
	case tok_call:
	    print_ast_type_left(ast);
	    bytes += print_ast_call(ast->l.value);
	    bytes += print_ast_paren_comma_list(ast->r.ast);
	    print_ast_type_right(ast);
	    break;
	case tok_proto:
	    bytes += print_ast_call(ast->l.value);
	    bytes += print_ast_paren_comma_list(ast->r.ast);
	    break;
	case tok_code:
	    dputc('{');		++bytes;
	    indent += 4;
	    if (ast->l.value)
		bytes += print_ast_list(ast->l.ast);
	    indent -= 4;
	    bytes += print_ast_newline();
	    dputc('}');		++bytes;
	    break;
	case tok_data:
	    dputc('{');		++bytes;
	    indent += 4;
	    bytes += print_ast_newline();
	    if (ast->l.value)
		bytes += print_ast_comma_list(ast->l.ast);
	    indent -= 4;
	    bytes += print_ast_newline();
	    dputc('}');		++bytes;
	    break;
	case tok_if:
	    bytes += dputs("if ", 3);
	    bytes += print_ast_paren_comma_list(ast->t.ast);
	    if (ast->l.ast->token == tok_code) {
		dputc(' ');	++bytes;
	    }
	    else {
		indent += 4;
		if (ast->l.ast->token != tok_stat)
		    bytes += print_ast_newline();
	    }
	    bytes += print_ast(ast->l.ast);
	    if (ast->l.ast->token != tok_code)
		indent -= 4;
	    if (ast->r.ast) {
		bytes += print_ast_newline();
		bytes += dputs("else", 4);
		if (ast->r.ast->token == tok_code) {
		    dputc(' ');	++bytes;
		}
		else {
		    indent += 4;
		    if (ast->r.ast->token != tok_stat)
			bytes += print_ast_newline();
		}
		bytes += print_ast(ast->r.ast);
		if (ast->r.ast->token != tok_code)
		    indent -= 4;
	    }
	    break;
	case tok_while:
	    bytes += dputs("while ", 6);
	    bytes += print_ast_paren_comma_list(ast->t.ast);
	    if (ast->c.ast->token == tok_code) {
		dputc(' ');	++bytes;
	    }
	    else {
		indent += 4;
		if (ast->c.ast->token != tok_stat)
		    bytes += print_ast_newline();
	    }
	    bytes += print_ast(ast->c.ast);
	    if (ast->c.ast->token != tok_code)
		indent -= 4;
	    break;
	case tok_for:
	    bytes += dputs("for (", 5);
	    if (ast->l.ast)
		bytes += print_ast_comma_list(ast->l.ast);
	    if (ast->t.ast) {
		bytes += dputs("; ", 2);
		bytes += print_ast_comma_list(ast->t.ast);
	    }
	    else {
		dputc(';');	++bytes;
	    }
	    if (ast->r.ast) {
		bytes += dputs("; ", 2);
		bytes += print_ast_comma_list(ast->r.ast);
	    }
	    else {
		dputc(';');	++bytes;
	    }
	    dputc(')');		++bytes;
	    if (ast->c.ast->token == tok_code) {
		dputc(' ');	++bytes;
	    }
	    else {
		indent += 4;
		if (ast->c.ast->token != tok_stat)
		    bytes += print_ast_newline();
	    }
	    bytes += print_ast(ast->c.ast);
	    if (ast->c.ast->token != tok_code)
		indent -= 4;
	    break;
	case tok_do:
	    bytes += dputs("do", 2);
	    if (ast->c.ast->token == tok_code) {
		dputc(' ');	++bytes;
	    }
	    else {
		indent += 4;
		if (ast->c.ast->token != tok_stat)
		    bytes += print_ast_newline();
	    }
	    bytes += print_ast(ast->c.ast);
	    if (ast->c.ast->token != tok_code) {
		indent -= 4;
		bytes += print_ast_newline();
	    }
	    else {
		dputc(' ');	++bytes;
	    }
	    bytes += dputs("while ", 6);
	    bytes += print_ast_paren_comma_list(ast->t.ast);
	    dputc(';');		++bytes;
	    break;
	case tok_label:
	    bytes += print_obj(ast->l.value);
	    dputc(':');		++bytes;
	    break;
	case tok_switch:
	    bytes += dputs("switch ", 7);
	    bytes += print_ast_paren_comma_list(ast->t.ast);
	    dputc(' ');		++bytes;
	    bytes += print_ast(ast->c.ast);
	    break;
	case tok_case:
	    bytes += dputs("case ", 5);
	    bytes += print_obj(ast->l.value);
	    if (ast->r.value) {
		bytes += dputs(" ... ", 5);
		bytes += print_obj(ast->r.value);
	    }
	    dputc(':');		++bytes;
	    break;
	case tok_default:
	    bytes += dputs("default:", 8);
	    break;
	case tok_elemref:
	    dputc('[');		++bytes;
	    bytes += print_ast_list(ast->l.ast);
	    dputc(']');		++bytes;
	    break;
	case tok_fieldref:
	    dputc('.');		++bytes;
	    bytes += print_ast(ast->l.ast);
	    break;
	case tok_function:
	    bytes += print_ast(ast->l.ast);
	    dputc(' ');		++bytes;
	    bytes += print_ast(ast->r.ast);
	    dputc(' ');		++bytes;
	    bytes += print_ast(ast->c.ast);
	    break;
	default:
	    bytes += print_ptr(ast);
	    break;
    }

    return (bytes);
}

static eword_t
write_ast_list(east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		bytes;

    for (bytes = 0; ast; ast = ast->next) {
	switch (ast->token) {
	    case tok_label:
	    case tok_case:
	    case tok_default:
		indent -= 4;
		bytes += print_ast_newline();
		indent += 4;
		break;
	    case tok_stat:
		break;
	    default:
		bytes += print_ast_newline();
		break;
	}
	bytes += print_ast(ast);
    }

    return (bytes);
}

static eword_t
write_ast_call(east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		 bytes;
    esymbol_t		*symbol;

    if (ast->token != tok_symbol) {
	assert(ast->token == tok_set || ast->token == tok_vector);
	bytes = print_ast(ast);
    }
    else {
	symbol = ast->l.value;
	bytes = print_sym(symbol);
    }

    return (bytes);
}

static eword_t
write_ast_unary(char *string, eword_t length,
		 east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		bytes;

    bytes = dputs(string, length);
    bytes += print_ast(ast->l.ast);

    return (bytes);
}

static eword_t
write_ast_binary(char *string, eword_t length,
		 east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		bytes;

    bytes = print_ast(ast->l.ast);
    dputc(' ');		++bytes;
    bytes += dputs(string, length);
    dputc(' ');		++bytes;
    bytes += print_ast(ast->r.ast);

    return (bytes);
}

static eword_t
write_ast_binary_function(char *string, eword_t length,
			  east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		bytes;

    bytes = dputs(string, length);
    dputc('(');		++bytes;
    bytes += print_ast(ast->l.ast);
    bytes += dputs(", ", 2);
    bytes += print_ast(ast->r.ast);
    dputc(')');		++bytes;

    return (bytes);
}

static eword_t
write_ast_comma_list(east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		bytes;

    if (ast) {
	bytes = print_ast(ast);
	for (ast = ast->next; ast; ast = ast->next) {
	    bytes += dputs(", ", 2);
	    bytes += print_ast(ast);
	}
    }
    else
	bytes = 0;

    return (bytes);
}

static eword_t
write_ast_paren_comma_list(east_t *ast, eint32_t indent, eformat_t *format)
{
    eword_t		bytes;

    dputc('(');		bytes = 1;
    if (ast)
	bytes += print_ast_comma_list(ast);
    dputc(')');		++bytes;

    return (bytes);
}

static eword_t
write_fields(erecord_t *record, eint32_t indent, eformat_t *format)
{
    eword_t		 bytes;
    eword_t		 offset;
    esymbol_t		*symbol;

    bytes = dputs(" {", 2);
    indent += 4;
    for (offset = 0; offset < record->vector->offset; offset++) {
	bytes += print_ast_newline();
	symbol = record->vector->v.ptr[offset];
	bytes += print_tag(symbol->tag, symbol, false);
	dputc(';');	++bytes;
    }
    indent -= 4;
    bytes += print_ast_newline();
    dputc('}');		++bytes;

    return (bytes);
}

static eword_t
write_tag(etag_t *tag, esymbol_t *name, ebool_t fields,
	  eint32_t indent, eformat_t *format)
{
    etag_t		*base;
    etag_t		*temp;
    eword_t		 bytes;
    erecord_t		*record;

    bytes = 0;
    if (fields && tag->type == tag_class)
	bytes = dputs("class ", 6);
    for (base = tag; base->base; base = base->base)
	;
    if ((record = base->name))
	bytes += print_sym(record->name);
    else
	bytes += dputs("auto", 4);
    if (fields && tag->type == tag_class)
	bytes += print_fields(record);
    if (name) {
	dputc(' ');		++bytes;
	bytes += print_sym(name);
    }
    for (temp = tag; temp != base; temp = temp->base) {
	if (temp->type == tag_vector) {
	    dputc('[');		++bytes;
	    bytes += print_int(*((evector_t *)temp->name)->v.w);
	    dputc(']');		++bytes;
	}
    }

    return (bytes);
}

static eword_t
write_ast_newline(eint32_t indent)
{
    eword_t		bytes;

    dputc('\n');	bytes = 1;
    for (; indent >= 8; indent -= 8, ++bytes)
	dputc('\t');
    for (; indent >= 4; indent -= 4)
	bytes += dputs("    ", 4);

    return (bytes);
}

static eword_t
write_ast_type(etype_t type, ebool_t left)
{
    eword_t		bytes;

    if (left) {
	dputc('<');		bytes = 1;
    }
    else
	bytes = dputs("</", 2);
    switch (type) {
	case t_void:		bytes += dputs("void", 4);		break;
	case t_null:		bytes += dputs("null", 4);		break;
	case t_bool:		bytes += dputs("bool", 4);		break;
	case t_half:		bytes += dputs("half", 4);		break;
	case t_word:		bytes += dputs("word", 4);		break;
	case t_float:		bytes += dputs("float", 5);		break;
	case t_int8:		bytes += dputs("int8", 4);		break;
	case t_uint8:		bytes += dputs("uint8", 5);		break;
	case t_int16:		bytes += dputs("int16", 5);		break;
	case t_uint16:		bytes += dputs("uint16", 6);		break;
	case t_uint32:		bytes += dputs("uint32", 6);		break;
#if __WORDSIZE == 32
	case t_int64:		bytes += dputs("int64", 5);		break;
#endif
	case t_uint64:		bytes += dputs("uint64", 6);		break;
	case t_single:		bytes += dputs("single", 6);		break;
	case t_rat:		bytes += dputs("rat", 3);		break;
	case t_mpz:		bytes += dputs("mpz", 3);		break;
	case t_mpq:		bytes += dputs("mpq", 3);		break;
	case t_mpr:		bytes += dputs("mpr", 3);		break;
	case t_cdd:		bytes += dputs("cdd", 3);		break;
	case t_cqq:		bytes += dputs("cqq", 3);		break;
	case t_mpc:		bytes += dputs("mpc", 3);		break;
	case t_integer:		bytes += dputs("integer", 7);		break;
	case t_exact:		bytes += dputs("exact", 5);		break;
	case t_inexact:		bytes += dputs("inexact", 7);		break;
	case t_real:		bytes += dputs("real", 4);		break;
	case t_number:		bytes += dputs("number", 6);		break;
	case t_undef:		bytes += dputs("undef", 5);		break;
	default:
	    if (type & t_vector)
		bytes += dputs("vector", 6);
	    else
		bytes += dputs("class", 5);
	    break;
    }
    dputc('>');		++bytes;

    return (bytes);
}
